/*
 *  ircd-hybrid: an advanced Internet Relay Chat Daemon(ircd).
 *  m_svs.c: for IRC Services. Anope/Atheme.
 *
 *  Copyright (C) 2003 Elias Shtift. ishtift@gmail.com
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 *  USA
 *
 *  $Id $
 */

#include "stdinc.h"
#include "fdlist.h"
#include "tools.h"
#include "common.h"  
#include "handlers.h"
#include "client.h"
#include "hash.h"
#include "channel.h"
#include "channel_mode.h"
#include "ircd.h"
#include "numeric.h"
#include "s_conf.h"
#include "s_misc.h"
#include "s_serv.h"
#include "s_user.h"
#include "send.h"
#include "list.h"
#include "irc_string.h"
#include "sprintf_irc.h"
#include "msg.h"
#include "parse.h"
#include "modules.h"
#include "hook.h"
#include "translate.h"

static void m_svsnick(struct Client *, struct Client *, int, char *[]);
static void m_svsmode(struct Client *, struct Client *, int, char *[]);
static void m_svsnoop(struct Client *, struct Client *, int, char *[]);
static void m_svshost(struct Client *, struct Client *, int, char *[]);
static void m_svsident(struct Client *, struct Client *, int, char *[]);
static void m_svsname(struct Client *, struct Client *, int, char *[]);
static void m_svsjoin(struct Client *, struct Client *, int, char *[]);
static void m_svspart(struct Client *, struct Client *, int, char *[]);

static int clean_nick_name(char *, int);


struct Message svsnick_msgtab = {
  "SVSNICK", 0, 0, 3, 0, MFLG_SLOW, 0,
  {m_unregistered, m_not_oper, m_svsnick, m_svsnick, m_svsnick, m_ignore}
};

struct Message svsmode_msgtab = {
  "SVSMODE", 0, 0, 3, 0, MFLG_SLOW, 0,
  {m_unregistered, m_not_oper, m_svsmode, m_svsmode, m_svsmode, m_ignore}
};

struct Message svsnoop_msgtab = {
  "SVSNOOP", 0, 0, 3, 0, MFLG_SLOW, 0,
  {m_unregistered, m_not_oper, m_svsnoop, m_svsnoop, m_svsnoop, m_ignore}
};

struct Message svshost_msgtab = {
  "SVSHOST", 0, 0, 3, 0, MFLG_SLOW, 0,
  {m_unregistered, m_not_oper, m_svshost, m_svshost, m_svshost, m_ignore}
};

struct Message svsident_msgtab = {
  "SVSIDENT", 0, 0, 3, 0, MFLG_SLOW, 0,
  {m_unregistered, m_not_oper, m_svsident, m_svsident, m_svsident, m_ignore}
};

struct Message svsname_msgtab = {
  "SVSNAME", 0, 0, 3, 0, MFLG_SLOW, 0,
  {m_unregistered, m_not_oper, m_svsname, m_svsname, m_svsname, m_ignore}
};

struct Message svsjoin_msgtab = {
  "SVSJOIN", 0, 0, 3, 0, MFLG_SLOW, 0,
  {m_unregistered, m_not_oper, m_svsjoin, m_svsjoin, m_svsjoin, m_ignore}
};

struct Message svspart_msgtab = {
  "SVSPART", 0, 0, 3, 0, MFLG_SLOW, 0,
  {m_unregistered, m_not_oper, m_svspart, m_svspart, m_svspart, m_ignore}
};

#ifndef STATIC_MODULES
void
_modinit(void)
{
  mod_add_cmd(&svsnick_msgtab);
  mod_add_cmd(&svsmode_msgtab);
  mod_add_cmd(&svsnoop_msgtab);
  mod_add_cmd(&svshost_msgtab);
  mod_add_cmd(&svsident_msgtab);
  mod_add_cmd(&svsname_msgtab);
  mod_add_cmd(&svsjoin_msgtab);
  mod_add_cmd(&svspart_msgtab);
  add_capability("SVS", CAP_SVS, 1);
}

void
_moddeinit(void)
{
  mod_del_cmd(&svsnick_msgtab);
  mod_del_cmd(&svsmode_msgtab);
  mod_del_cmd(&svsnoop_msgtab);
  mod_del_cmd(&svshost_msgtab);
  mod_del_cmd(&svsident_msgtab);
  mod_del_cmd(&svsname_msgtab);
  mod_del_cmd(&svsjoin_msgtab);
  mod_del_cmd(&svspart_msgtab);
  delete_capability("SVS");
}

const char *_version = "$Revision: 1 $";
#endif

/*
 * ms_svsnick()
 *
 * parv[0] = sender prefix
 * parv[1] = user to force
 * parv[2] = nick to force them to
 */

static void m_svsnick(struct Client *client_p, struct Client *source_p,
                      int parc, char *parv[])
{
  char	    nick[NICKLEN];
  struct    Client *target_p;

  if (!find_matching_name_conf(ULINE_TYPE, source_p->name,
                               "*", "*", SHARED_SVS) &&
      !IsServer(source_p))
    return;

  if ((target_p = find_person(client_p, parv[1])) == NULL)
    return;

  if (MyConnect(target_p))
  {
    strlcpy(nick, parv[2], sizeof(nick));

    if (!clean_nick_name(nick, 1))
      return;
    if (find_client(nick) != NULL)
      return;

      target_p->localClient->number_of_nick_changes = 0;
      change_local_nick(&me, target_p, nick);
      target_p->localClient->number_of_nick_changes = ConfigFileEntry.max_nick_changes;
  }
  else
    sendto_server(client_p, source_p, NULL, CAP_SVS, NOCAPS, NOFLAGS,
                  ":%s SVSNICK %s %s", source_p->name, parv[1], parv[2]);
}


/*
 * ms_svsmode()
 *
 * parv[0] = sender prefix
 * parv[1] = user
 * parv[2] = modes
 */

static void m_svsmode(struct Client *client_p, struct Client *source_p,
                      int parc, char *parv[])
{
  struct Client *target_p;
  char **p, *m;
  unsigned int what = 1, oldflags, flag;

  if (!find_matching_name_conf(ULINE_TYPE, source_p->name,
                               "*", "*", SHARED_SVS) &&
      !IsServer(source_p))
    return;

  if (! (target_p = find_client(parv[1])))
    return;

  if (MyConnect(target_p))
  {
    oldflags = target_p->umodes;
    for (p = &parv[2]; p && *p; p++)
    {
      for (m = *p; *m; m++)
      {
        switch (*m)
	{
	  case '+':
	    what = 1;
	    break;
	  case '-':
	    what = 0;
	    break;
	  case ' ' :
	  case '\n':
	  case '\r':
	  case '\t':
	    break;
	  
	  default:
	    if ((flag = user_modes[(unsigned char)*m]))
	    {
	      execute_callback(umode_cb, target_p, target_p, what, flag);
	    }
	}
      }
    }
    send_umode_out(target_p, target_p, oldflags);
  }
  else
    sendto_server(client_p, source_p, NULL, CAP_SVS, NOCAPS, NOFLAGS,
                  ":%s SVSMODE %s %s", source_p->name, parv[1], parv[2]);
}

/*
 * ms_svshost()
 *
 * parv[0] = sender prefix
 * parv[1] = user
 * parv[2] = new host
 */

static void m_svshost(struct Client *client_p, struct Client *source_p,
                      int parc, char *parv[])
{
  struct Client *target_p;

  if (!IsServer(source_p) ||
      !find_matching_name_conf(ULINE_TYPE, source_p->name,
                               "*", "*", SHARED_SVS))
    return;

  if (! (target_p = find_client(parv[1])))
    return;

  if (strlen(parv[2]) > HOSTLEN || !*parv[2] || !valid_hostname(parv[2]))
    return;

  delete_user_host(target_p->username, target_p->host, !MyConnect(target_p));
  strlcpy(target_p->host, parv[2], sizeof(target_p->host));
  if (MyConnect(target_p))
  {
    SetIPSpoof(target_p);
    SetVHost(target_p);
  }
  add_user_host(target_p->username, target_p->host, !MyConnect(target_p));

  sendto_server(client_p, source_p, NULL, CAP_SVS, NOCAPS, NOFLAGS,
                ":%s SVSHOST %s %s", source_p->name, parv[1], parv[2]);
}

/*
 * ms_svsident()
 *
 * parv[0] = sender prefix
 * parv[1] = user
 * parv[2] = new ident
 */

static void m_svsident(struct Client *client_p, struct Client *source_p,
                      int parc, char *parv[])
{
  struct Client *target_p;

  if (!find_matching_name_conf(ULINE_TYPE, source_p->name,
                               "*", "*", SHARED_SVS) ||
      !IsServer(source_p))
    return;

  if (! (target_p = find_client(parv[1])))
    return;
  if (strlen(parv[2]) > USERLEN || !*parv[2] || !valid_username(parv[2]))
    return;

  strcpy(target_p->username, parv[2]);

  sendto_server(client_p, source_p, NULL, CAP_SVS, NOCAPS, NOFLAGS,
                ":%s SVSIDENT %s %s", source_p->name, parv[1], parv[2]);
}

/*
 * ms_svsname()
 *
 * parv[0] = sender prefix
 * parv[1] = user
 * parv[2] = new name
 */

static void m_svsname(struct Client *client_p, struct Client *source_p,
                      int parc, char *parv[])
{
  struct Client *target_p;

  if (!find_matching_name_conf(ULINE_TYPE, source_p->name,
                               "*", "*", SHARED_SVS) ||
      !IsServer(source_p))
    return;

  if (! (target_p = find_client(parv[1])))
    return;
  if (strlen(parv[2]) > REALLEN || !*parv[2]) 
    return;

  strcpy(target_p->info, parv[2]);

  sendto_server(client_p, source_p, NULL, CAP_SVS, NOCAPS, NOFLAGS,
                ":%s SVSNAME %s %s", source_p->name, parv[1], parv[2]);
}

/*
 * ms_svsjoin()
 *
 *
 */

static void m_svsjoin(struct Client *client_p, struct Client *source_p,
                      int parc, char *parv[])
{
  struct Client *target_p = NULL;
  struct Channel *chptr = NULL;
  unsigned int type = 0;
  char mode = '\0';
  char sjmode = '\0';
  char *newch = NULL;
  dlink_node *ptr = NULL;

  if (!find_matching_name_conf(ULINE_TYPE, source_p->name,
                               "*", "*", SHARED_SVS) &&
      !IsServer(source_p))
    return;

  if ((target_p = find_client(parv[1])) == NULL)
    return;

  if (!MyConnect(target_p))
  {
    sendto_server(client_p, source_p, NULL, CAP_SVS, NOCAPS, NOFLAGS,
                  ":%s SVSJOIN %s %s", source_p->name, parv[1], parv[2]);
    return;
  }

  if ((chptr = hash_find_channel(parv[2])) != NULL)
  {
    if (IsMember(target_p, chptr))
    {
      sendto_one(source_p, ":%s NOTICE %s :*** Notice -- %s is already in %s",
                 me.name, source_p->name, target_p->name, chptr->chname);
      return;
    }

    add_user_to_channel(chptr, target_p, type, NO);

    sendto_channel_local(ALL_MEMBERS, NO, chptr, ":%s!%s@%s JOIN :%s",
                         target_p->name, target_p->username,
                         target_p->host, chptr->chname);

    if (sjmode)
      sendto_channel_local(ALL_MEMBERS, NO, chptr, ":%s MODE %s +%c %s",
                           me.name, chptr->chname, mode, target_p->name);

    if (chptr->chname[0] == '#')
    {
      if (sjmode)
      {
        DLINK_FOREACH (ptr, serv_list.head)
        {
          struct Client *serv_p = ptr->data;
          if (serv_p == target_p->from || IsDead(serv_p))
            continue;

          sendto_one(serv_p, ":%s SJOIN %lu %s + :%c%s",
                     ID_or_name(&me, serv_p), (unsigned long)chptr->channelts,
                     chptr->chname, (sjmode == '%' &&
                     !IsCapable(serv_p, CAP_HOPS)) ? '@' : sjmode,
                     ID_or_name(target_p, serv_p));
        }
      }
      else
      {
        sendto_server(target_p, target_p, chptr, CAP_TS6, NOCAPS, LL_ICLIENT,
                      ":%s SJOIN %lu %s + :%s",
                      me.id, (unsigned long)chptr->channelts,
                      chptr->chname, target_p->id);
        sendto_server(target_p, target_p, chptr, NOCAPS, CAP_TS6, LL_ICLIENT,
                      ":%s SJOIN %lu %s + :%s",
                      me.name, (unsigned long)chptr->channelts,
                      chptr->chname, target_p->name);
      }
    }

    if (chptr->topic != NULL)
    {
      sendto_one(target_p, form_str(RPL_TOPIC),
                 me.name, target_p->name,
                 chptr->chname, chptr->topic);
      sendto_one(target_p, form_str(RPL_TOPICWHOTIME),
                 me.name, target_p->name, chptr->chname,
                 chptr->topic_info, chptr->topic_time);
    }

    target_p->localClient->last_join_time = CurrentTime;
    channel_member_names(target_p, chptr, 1);
  }
  else
  {
    newch = parv[2];

    if (!check_channel_name(newch, 1))
    {
      sendto_one(source_p, form_str(ERR_BADCHANNAME),
                 me.name, source_p->name, newch);
      return;
    }

    /*
     * it would be interesting here to allow an oper
     * to force target_p into a channel that doesn't exist
     * even more so, into a local channel when we disable
     * local channels... but...
     * I don't want to break anything - scuzzy
     */
    if (ConfigChannel.disable_local_channels && (*newch == '&'))
    {
      sendto_one(source_p, form_str(ERR_NOSUCHCHANNEL),
                 me.name, source_p->name, newch);
      return;
    }

    chptr = make_channel(newch);
    add_user_to_channel(chptr, target_p, CHFL_CHANOP, NO);

    /* send out a join, make target_p join chptr */
    if (chptr->chname[0] == '#')
    {
      sendto_server(target_p, target_p, chptr, CAP_TS6, NOCAPS, LL_ICLIENT,
                    ":%s SJOIN %lu %s +nt :@%s",
                    me.id, (unsigned long)chptr->channelts,
                    chptr->chname, ID(target_p));
      sendto_server(target_p, target_p, chptr, NOCAPS, CAP_TS6, LL_ICLIENT,
                    ":%s SJOIN %lu %s +nt :@%s",
                    me.name, (unsigned long)chptr->channelts,
                    chptr->chname, target_p->name);
    }

    sendto_channel_local(ALL_MEMBERS, NO, chptr, ":%s!%s@%s JOIN :%s",
                         target_p->name, target_p->username,
                         target_p->host, chptr->chname);

    chptr->mode.mode |= MODE_TOPICLIMIT | MODE_NOPRIVMSGS;

    sendto_channel_local(ALL_MEMBERS, NO, chptr, ":%s MODE %s +nt",
                         me.name, chptr->chname);

    target_p->localClient->last_join_time = CurrentTime;
    channel_member_names(target_p, chptr, 1);
  }
}

/*
 * ms_svspart()
 *
 *
 */

static void m_svspart(struct Client *client_p, struct Client *source_p,
                      int parc, char *parv[])
{
  struct Client *target_p = NULL;
  struct Channel *chptr = NULL;
  struct Membership *member = NULL;

  if (!find_matching_name_conf(ULINE_TYPE, source_p->name,
                               "*", "*", SHARED_SVS) &&
      !IsServer(source_p))
    return;

  /* if target_p == NULL then let the oper know */
  if ((target_p = find_client(parv[1])) == NULL)
    return;

  if (!MyConnect(target_p))
  {
    sendto_server(client_p, source_p, NULL, CAP_SVS, NOCAPS, NOFLAGS,
                  ":%s SVSPART %s %s", source_p->name, parv[1], parv[2]);
    return;
  }

  if ((chptr = hash_find_channel(parv[2])) == NULL)
  {
    return;
  }

  if ((member = find_channel_link(target_p, chptr)) == NULL)
  {
    return;
  }

  if (chptr->chname[0] == '#')
  {
    sendto_server(target_p, target_p, chptr, CAP_TS6, NOCAPS, LL_ICLIENT,
                  ":%s PART %s :%s", ID(target_p),
                  chptr->chname, target_p->name);
    sendto_server(target_p, target_p, chptr, NOCAPS, CAP_TS6, LL_ICLIENT,
                  ":%s PART %s :%s", target_p->name,
                  chptr->chname, target_p->name);
  }

  sendto_channel_local(ALL_MEMBERS, NO, chptr, ":%s!%s@%s PART %s :%s",
                       target_p->name, target_p->username,
                       target_p->host, chptr->chname,
                       target_p->name);
  remove_user_from_channel(member);
}

/*
 * ms_svsnoop()
 *
 * parv[0] = sender prefix
 * parv[1] = value
 */

static void m_svsnoop(struct Client *client_p, struct Client *source_p,
                      int parc, char *parv[])
{
  if (find_matching_name_conf(ULINE_TYPE, source_p->name,
                               "*", "*", SHARED_SVS) &&
      !irccmp(me.name, parv[1]))
     ConfigFileEntry.noop = atoi(parv[2]);
}

/*
 * clean_nick_name()
 *
 * input        - nickname
 * output       - none
 * side effects - walks through the nickname, returning 0 if erroneous
 */
static int
clean_nick_name(char *nick, int local)
{
  assert(nick);

  /* nicks can't start with a digit or - or be 0 length */
  /* This closer duplicates behaviour of hybrid-6 */

  if (*nick == '-' || (IsDigit(*nick) && local) || *nick == '\0')
    return 0;

  for (; *nick; ++nick)
    if (!IsNickChar(*nick))
      return 0;

  return 1;
}
